package com.terheyden.pluginLoaderDemo;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ServiceLoader;

import com.terheyden.pluginLoaderDemo.plugins.api.AppEnvironment;
import com.terheyden.pluginLoaderDemo.plugins.api.Plugin;

/**
 * Handles dynamically finding and loading plugins.
 */
public class PluginManager {

    /**
     * Dynamically find plugins and load them.
     * @param env this gets passed to all plugins so they can access our methods and objects
     * @param pluginSubdir the subdir where the plugins should be (e.g. "/plugins")
     */
    public static void loadPlugins(AppEnvironment env, String pluginSubdir) {

        // Create a class loader for the plugins dir.
        URLClassLoader pluginsClassLoader = getPluginsClassLoader(pluginSubdir);

        // Use ServiceLoader to dynamically find all classes that implement the Plugin interface.
        // FYI this also works for abstract classes.
        ServiceLoader<Plugin> pluginLoader = ServiceLoader.load(Plugin.class, pluginsClassLoader);

        // Load all plugins:

        for (Plugin plugin : pluginLoader) {

            try {

                System.out.println(String.format("Loading plugin: %s", plugin.getClass().getSimpleName()));
                plugin.load(env);

            } catch (Exception e) {
                // Never trust plugins or any 3rd party objects, so catch all exceptions.
                System.err.println("Error loading plugin:");
                e.printStackTrace();
            }
        }
    }

    /**
     * Create a class loader specifically for the .jars in the plugins folder.
     * @param pluginSubdir the subdir where the plugins should be (e.g. "/plugins")
     */
    private static URLClassLoader getPluginsClassLoader(String pluginSubdir) {

        // Get the dir the java app was started in.
        String baseDir = System.getProperty("user.dir");

        // Find or create /plugins dir.
        File pluginDir = new File(baseDir + pluginSubdir);

        if (!pluginDir.exists()) {
            pluginDir.mkdir();
        }

        URL[] pluginJars = getJars(pluginDir);
        return URLClassLoader.newInstance(pluginJars);
    }

    /**
     * Find all JAR files in the specified directory,
     * return them as an array of URLs.
     */
    private static URL[] getJars(File dir) {

        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            throw new RuntimeException("Invalid dir.");
        }

        // Only interested in .JAR files:
        File[] jarFiles = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().toLowerCase().endsWith(".jar");
            }
        });

        return getFileUrls(jarFiles);
    }

    /**
     * Convert a File[] array to a URL[] array.
     */
    private static URL[] getFileUrls(File[] files) {

        URL[] urls = new URL[files.length];

            for (int i = 0; i < files.length; i++) {

                try {

                    urls[i] = files[i].toURI().toURL();

                } catch (Exception e) {
                    // Catch all exceptions to avoid one file mucking up the rest.
                    System.err.println("Error preparing plugin file: " + files[i].getAbsolutePath());
                    e.printStackTrace();
                }
            }

        return urls;
    }
}
